package scatter.captcha.rest.tools;

import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.math.MathUtil;
import cn.hutool.core.util.RandomUtil;
import lombok.Getter;
import lombok.Setter;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;

/**
 * 滑动验证码工具类
 */
public class SlideVerifyCodeTools {

    // 定义四个边的数组
    private static String[] borders = new String[]{"1","2","3","4"};
    // 边组合缓存
    private static Map<Integer, List<String[]>> selectCache = new HashMap<>();

    /**
     * 边的数据载体
     */
    @Getter
    @Setter
    private static class BorderDto{
        // 边编号
        private int borderNum;
        // 是否凹陷 true=凹陷，false=凸出
        private boolean isHollow;

        // 圆的坐标x
        private int circleX;
        // 圆的坐标y
        private int circleY;

        // 动态计算的平方
        private double power;
    }

    /**
     * 处理完成的图片结果
     */
    @Setter
    @Getter
    public static class cutResult{
        // 背景图
        private BufferedImage bg;
        // 背景图编码
        private String bgBase64;
        // 滑动图
        private BufferedImage block;
        // 滑动图编码
        private String blockBase64;
        // 滑块偏移量x
        private int blockOffsetX;
        // 滑块偏移量y
        private int blockOffsetY;
    }
    /**
     * 获取边数据
     * @param borderCount 边的数量，可选值 1到4
     * @return
     */
    private static List<BorderDto> getBorderDtoData(int borderCount){
        List<String[]> combinationSelect = selectCache.get(borderCount);
        if (combinationSelect == null) {
            combinationSelect = MathUtil.combinationSelect(borders, borderCount);
            selectCache.put(borderCount, combinationSelect);
        }

        // 随机一个索引
        int randomIndex = RandomUtil.randomInt(combinationSelect.size());
        // 取出该索引，该数据的长度为borderCount ，是选择的边的编号
        String[] selectedBorderNumStr = combinationSelect.get(randomIndex);
        // 转为int数组
        int[] selectedBorderNumInt = Arrays.stream(selectedBorderNumStr).mapToInt(Integer::valueOf).toArray();
        List<BorderDto> borderDtos = new ArrayList<>(borderCount);
        BorderDto bd = null;
        for (int borderNum : selectedBorderNumInt) {
            bd = new BorderDto();
            bd.setBorderNum(borderNum);
            bd.setHollow(RandomUtil.randomBoolean());

            borderDtos.add(bd);
        }
        return borderDtos;
    }

    /**
     * 填充圆的坐标
     * @param bd
     */
    private static void fillCircleCoordinate(BorderDto bd,int width,int height,int circleR) {
        switch (bd.getBorderNum()){
            case 1: {
                bd.setCircleY(RandomUtil.randomInt(circleR,height - circleR));
                if(bd.isHollow()){
                    bd.setCircleX(RandomUtil.randomInt(circleR));
                }else {
                    bd.setCircleX(RandomUtil.randomInt(circleR,circleR + circleR/2));
                }

                break;
            }
            case 2: {
                bd.setCircleX(RandomUtil.randomInt(circleR,width - circleR));
                if(bd.isHollow()){
                    bd.setCircleY(RandomUtil.randomInt(circleR));
                }else {
                    bd.setCircleY(RandomUtil.randomInt(circleR,circleR + circleR/2));
                }
                break;
            }
            case 3: {
                bd.setCircleY(RandomUtil.randomInt(circleR,height - circleR));
                if(bd.isHollow()){
                    bd.setCircleX(RandomUtil.randomInt(width-circleR,width));
                }else {
                    bd.setCircleX(RandomUtil.randomInt(width-circleR-circleR/2,width-circleR));
                }
                break;
            }
            case 4: {
                bd.setCircleX(RandomUtil.randomInt(circleR,width - circleR));
                if(bd.isHollow()){
                    bd.setCircleY(RandomUtil.randomInt(height - circleR,height));
                }else {
                    bd.setCircleY(RandomUtil.randomInt(height-circleR-circleR/2,height-circleR));
                }
                break;
            }
            default: {

            }
        }
    }
    /**
     * 生成小图轮廓矩阵
     * 矩阵值1表示有像素，0表示透明
     * 小图原始应该是一个长方形，标记四个边编号如下
     *         2
     *     |---------|
     *     |         |
     *   1 |         | 3
     *     |---------|
     *          4
     * @return
     */
    private static int[][] getBlockData(int width,int height,int circleR,int borderNum) {
        // 从1到4随机一个边数，获取边数据
        List<BorderDto> borderDtoData = getBorderDtoData(borderNum);
        // 带圆的坐标
        for (BorderDto bd : borderDtoData) {
            fillCircleCoordinate(bd,width,height,circleR);
        }

        // 整个图像块的大小，每一个值代表一个像素点
        int[][] data = new int[width][height];
        double po = Math.pow(circleR,2);
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                data[i][j] = 1;
                for (BorderDto bd : borderDtoData) {
                    bd.setPower(Math.pow(i - bd.getCircleX(),2) + Math.pow(j - bd.getCircleY(),2));
                    // 凹陷
                    if (bd.isHollow()) {
                        if(bd.getPower() <= po){
                            data[i][j] = 0;
                        }
                    }else{
                        // 凸出
                        if(bd.getPower() >= po){
                            int d = circleR;
                            switch (bd.getBorderNum()) {
                                case 1: {
                                    if (i <= d) {
                                        data[i][j] = 0;
                                    }
                                    break;
                                }
                                case 2: {
                                    if (j <= d) {
                                        data[i][j] = 0;
                                    }
                                    break;
                                }
                                case 3: {
                                    if (i >= width -d) {
                                        data[i][j] = 0;
                                    }
                                    break;
                                }
                                case 4: {
                                    if (j >= height -d) {
                                        data[i][j] = 0;
                                    }
                                    break;
                                }
                                default: {

                                }
                            }
                        }
                    }
                }
            }
        }
        return data;
    }

    /**
     * 抠图处理
     * @param originBgImageStream 原背景图输入流
     * @param templateImage 模板数据
     * @param blockOffsetX 模板图偏移量x
     * @param blockOffsetY 模板图偏移量y
     * @return
     * @throws IOException
     */
    private static cutResult cutByTemplate(InputStream originBgImageStream, int[][] templateImage, int blockOffsetX, int blockOffsetY) throws IOException {
        BufferedImage oriImage = ImageIO.read(originBgImageStream);
        return cutByTemplate(oriImage,templateImage,blockOffsetX,blockOffsetY);
    }

    /**
     * 抠图处理
     * @param oriImage 原背景
     * @param templateImage 模板数据
     * @param blockOffsetX 模板图偏移量x
     * @param blockOffsetY 模板图偏移量y
     * @return
     * @throws IOException
     */
    private static cutResult cutByTemplate(BufferedImage oriImage, int[][] templateImage, int blockOffsetX, int blockOffsetY) throws IOException {
        int blockWidth = templateImage[0].length;
        int blockHeight = templateImage.length;
        oriImage = ImgUtil.copyImage(oriImage, BufferedImage.TYPE_4BYTE_ABGR);
        BufferedImage blockImage= new BufferedImage(blockWidth, blockHeight, BufferedImage.TYPE_4BYTE_ABGR);
        for (int i = 0; i < blockWidth; i++) {
            for (int j = 0; j < blockHeight; j++) {
                int rgb = templateImage[i][j];
                // 原图中对应位置变色处理
                int rgb_ori = oriImage.getRGB(blockOffsetX + i, blockOffsetY + j);
                if (rgb == 1) {
                    //抠图上复制对应颜色值
                    blockImage.setRGB(i, j, rgb_ori);
                    //原图对应位置颜色变化
                    oriImage.setRGB(blockOffsetX + i, blockOffsetY + j, ((5 * 255 / 10) << 24) | (rgb_ori & 0x00ffffff) );
                }else{
                    //这里把背景设为透明
                    blockImage.setRGB(i, j, rgb_ori & 0x00ffffff);
                }
            }
        }
        cutResult cutResult = new cutResult();
        cutResult.setBg(oriImage);
        cutResult.setBgBase64(ImgUtil.toBase64DataUri(oriImage,ImgUtil.IMAGE_TYPE_JPG));
        cutResult.setBlock(blockImage);
        cutResult.setBlockBase64(ImgUtil.toBase64DataUri(blockImage,ImgUtil.IMAGE_TYPE_PNG));
        cutResult.setBlockOffsetX(blockOffsetX);
        cutResult.setBlockOffsetY(blockOffsetY);
        return cutResult;
    }

    /**
     * 创建滑动背景和滑块图
     * @param imageFileAbsolutePath 背景图路径
     * @param width 滑块图宽度
     * @param height 滑块图高度
     * @param circleR 凹凸圆半径
     * @param positionX 滑块偏移量x
     * @param positionY 滑块偏移量y
     * @return
     */
    public static cutResult createImage(String imageFileAbsolutePath, int width, int height,int circleR,int borderNum,int positionX,int positionY) throws IOException {
        return cutByTemplate(new FileInputStream(imageFileAbsolutePath),getBlockData(width, height,circleR,borderNum),positionX,positionY);
    }


    /**
     * 创建滑动背景和滑块图
     * @param originBgImage 背景图
     * @param width 滑块图宽度
     * @param height 滑块图高度
     * @param circleR 凹凸圆半径
     * @param positionX 滑块偏移量x
     * @param positionY 滑块偏移量y
     * @return
     */
    public static cutResult createImage(BufferedImage originBgImage, int width, int height,int circleR,int borderNum,int positionX,int positionY) throws IOException {
        return cutByTemplate(originBgImage,getBlockData(width, height,circleR,borderNum),positionX,positionY);
    }
}
